home *** CD-ROM | disk | FTP | other *** search
/ Turnbull China Bikeride / Turnbull China Bikeride - Disc 1.iso / DEMON / UTILS / EXTARCT.ARC / c / ExTARct
Text File  |  1995-08-20  |  19KB  |  619 lines

  1. /* ExTARct.c
  2.  * Tar file extractor.
  3.  * (C) 1995 Kevin F. Quinn
  4.  * Email: kevq@banana.demon.co.uk
  5.  *
  6.  * Rationale - to provide an automated facility for extracting tar archives
  7.  * in a RiscOS-friendly way, i.e. reversing name/type, and inserting an extra
  8.  * directory level to cope with the (grr!) 77-entry directory limit.
  9.  *
  10.  * Syntax:
  11.  * ExTARct [options] <tarfile>
  12.  *   where options are as follows:
  13.  *     -v            The ubiquitous verbose option
  14.  *     -d <number>   The number of files to one directory (default 60)
  15.  *     -t <filename> Filename listing types to swap, tagged for directory split
  16.  *                   (default is no swaps, no directory splits)
  17.  *     -l            List operations (i.e. file mappings) without doing anything really
  18.  *     -p <prefix>   Prefix for directory splits (default none)
  19.  *     -c            Enable filename crunching (uses fileswitch truncation by default)
  20.  *                   Unimplemented as yet.
  21.  *
  22.  */
  23.  
  24. /*
  25.  * Version 0.1
  26.  * Crunching not supported yet.
  27.  * Gets very slow when it is searching split subdirectories.
  28.  */
  29.  
  30. #include <stdlib.h>
  31. #include <stdio.h>
  32. #include <string.h>
  33.  
  34. #include "tar.h"
  35. #include "kernel.h"
  36. #include "swis.h"
  37.  
  38. #define FALSE (0==1)
  39. #define TRUE  (1==1)
  40.  
  41. #define MAXTYPES 256
  42. #define MAXLEN 256
  43. #define MAXENTS 77
  44. #define BUFSIZE 78*256
  45.  
  46. static int dsize;
  47. static int verbose;
  48. static int list;
  49. static int crunch;
  50. static char *types[MAXTYPES];
  51. static int splits[MAXTYPES];
  52. static int tc;
  53. static char *prefix;
  54.  
  55. static char buf[BUFSIZE];
  56.  
  57.  
  58. int makedir(char *filename) {
  59.   char directory[MAXLEN];
  60.   char *cptr;
  61.   _kernel_oserror *kerr=NULL;
  62.   _kernel_swi_regs rin, rout;
  63.  
  64.   if (*filename=='\0') return(FALSE);
  65.   cptr=filename+strlen(filename);
  66.   while (cptr>filename && *cptr!='.') cptr--;
  67.   if (*cptr=='.') {
  68.     strcpy(directory,filename);
  69.     directory[cptr-filename]='\0';
  70.     rin.r[0]=8;
  71.     rin.r[1]=(int)directory;
  72.     rin.r[2]=0; /* default directory size */
  73.     kerr=_kernel_swi(OS_File,&rin,&rout);
  74.     if (kerr==NULL) {
  75.       return(TRUE);
  76.     } else {
  77.       if (makedir(directory)) {
  78.         return(makedir(filename));
  79.       } else {
  80.         return(FALSE);
  81.       }
  82.     }
  83.   } else {
  84.     return(TRUE); /* No directory; no need to create */
  85.   }
  86. }
  87.  
  88. /* Convert octal string to number */
  89. static int otoi(char *str) {
  90.   int num=0;
  91.   while (*str==' ') str++;
  92.   while (*str>='0' && *str<'8') {
  93.     num=num*8+((*str)-'0');
  94.     str++;
  95.   }
  96.   return(num);
  97. }
  98.  
  99.  
  100. static void roify(char *str) {
  101.   /* Convert the following:
  102.    *
  103.    *   / -> .
  104.    *   . -> /
  105.    *   :,*,#,$,&,@,^,%,\,|,",SPACE  ->  ~<n>  (where n=0..9,A,B)
  106.    *   ~ -> ~~
  107.    *
  108.    * Note that this conversion is reversible.
  109.    * Note also that no check is performed that MAXLEN is not exceeded,
  110.    * which in theory is possible.  However, tar names are 100 chars max,
  111.    * so the problem should not arise.
  112.    */
  113.    char tstr[MAXLEN];
  114.    int i=0;
  115.    strcpy(tstr,str);
  116.    while (tstr[i]!='\0') {
  117.      switch (tstr[i]) {
  118.        /* Swap directory and filetype separators*/
  119.        case '/':  *str='.'; break;
  120.        case '.':  *str='/'; break;
  121.  
  122.        /* Escape the nasties */
  123.        case ':':  *str++='~'; *str='0'; break;
  124.        case '*':  *str++='~'; *str='1'; break;
  125.        case '#':  *str++='~'; *str='2'; break;
  126.        case '$':  *str++='~'; *str='3'; break;
  127.        case '&':  *str++='~'; *str='4'; break;
  128.        case '@':  *str++='~'; *str='5'; break;
  129.        case '^':  *str++='~'; *str='6'; break;
  130.        case '%':  *str++='~'; *str='7'; break;
  131.        case '\\': *str++='~'; *str='8'; break;
  132.        case '|':  *str++='~'; *str='9'; break;
  133.        case '"':  *str++='~'; *str='A'; break;
  134.        case ' ':  *str++='~'; *str='B'; break;
  135.        case '~':  *str++='~'; *str='~'; break;
  136.  
  137.        default:
  138.          /* character is OK */;
  139.      }
  140.      i++;
  141.      str++;
  142.    }
  143. }
  144.  
  145.  
  146. static int translatename(const char *tarname, char *roname) {
  147.   /* Right; this is the cunning bit.
  148.    *
  149.    * Name translation.
  150.    * split to Directory,filename, converting /->., .->/ in directory name
  151.    * split filename to name.type (search back from end to first '.')
  152.    * if name is empty, return FALSE
  153.    * if no type
  154.    *   roname=directory.name
  155.    * else
  156.    *   if type is in types list
  157.    *     if splits[n] (& in types list)
  158.    *       find first subdirectory in directory 1,2,3... which has less than dsize entries
  159.    *       roname=directory.sub.type.name
  160.    *     else
  161.    *       roname=directory.type.name
  162.    *   else
  163.    *     roname=directory.name/type
  164.    * return TRUE;
  165.    */
  166.  
  167.   char tname[MAXLEN];
  168.   char *direct;
  169.   char *name;
  170.   char *type;
  171.  
  172.   char *tptr;
  173.   int tn, fc;
  174.   int found;
  175.   int subnum;
  176.   char scratch[MAXLEN];
  177.   _kernel_oserror *kerr=NULL;
  178.   _kernel_swi_regs rin, rout;
  179.   int tmpind;
  180.  
  181.   strcpy(tname,tarname);
  182.   tptr=tname+strlen(tname);
  183.   while (tptr>tname && *tptr!='/') tptr--;
  184.   if (*tptr!='/') {
  185.     direct=NULL;
  186.     name=tname;
  187.   } else {
  188.     direct=tname;
  189.     *tptr='\0';
  190.     name=tptr+1;
  191.   }
  192.   if (*name!='\0') { /* no name => just a directory */
  193.     tptr=name+strlen(name);
  194.     while (tptr>name && *tptr!='.') tptr--;
  195.     if (*tptr!='.') {
  196.       type=NULL;
  197.     } else {
  198.       *tptr='\0';
  199.       type=tptr+1;
  200.     }
  201.   } else {
  202.     name=NULL;
  203.     type=NULL;
  204.   }/* if not just a directory */
  205.   /* Sort out any RO special character problems */
  206.   if (direct!=NULL) roify(direct);
  207.   if (name!=NULL)   roify(name);
  208.   if (type!=NULL)   roify(type);
  209.  
  210.   if (name!=NULL) {/* if not just a directory */
  211.     if (type==NULL) {
  212.       /* roname=directory.name*/
  213.       if (direct!=NULL) {
  214.         strcpy(roname,direct);
  215.         tptr=roname+strlen(direct);
  216.         *tptr++='.';
  217.         *tptr='\0';
  218.       } else {
  219.         tptr=roname;
  220.       }
  221.       strcpy(tptr,name);
  222.     } else {
  223.       /* if type is in types list*/
  224.       tn=0;
  225.       /* The 'while' condition below avoids evaluating types[tn] when no types are valid (i.e. tn=tc=0) */
  226.       while ((tn<tc)?(strcmp(types[tn],type)!=0):FALSE) tn++;
  227.       if (tn<tc) {
  228.         /*
  229.          * find first subdirectory in directory 1,2,3... which has less than dsize entries
  230.          * roname=directory.sub.type.name
  231.          */
  232.         if (direct!=NULL) {
  233.           strcpy(scratch,direct);
  234.           tptr=scratch+strlen(direct);
  235.           *tptr++='.';
  236.           *tptr='\0';
  237.         } else {
  238.           tptr=scratch;
  239.           *tptr='\0';
  240.         }
  241.         if (splits[tn]) {
  242.           if (prefix!=NULL) {
  243.             strcpy(tptr,prefix);
  244.             tptr=tptr+strlen(prefix);
  245.             *tptr='\0';
  246.           }
  247.           subnum=0;
  248.           found=FALSE;
  249.           while (!found) {
  250.             tptr[0]=(subnum / 10)+'0';
  251.             tptr[1]=(subnum % 10)+'0';
  252.             tptr[2]='.';
  253.             tmpind=0;
  254.             while (type[tmpind]>'\0') {  /* if splits[tn], type!=NULL... */
  255.               tptr[3+tmpind]=type[tmpind];
  256.               tmpind++;
  257.             }
  258.             tptr[3+tmpind]='\0';
  259.             fc=0;
  260.             rin.r[0]=9;
  261.             rin.r[1]=(int)scratch;
  262.             rin.r[2]=(int)buf;
  263.             rin.r[3]=MAXENTS;
  264.             rin.r[4]=0;
  265.             rin.r[5]=BUFSIZE;
  266.             rin.r[6]=(int)"*";
  267.             while ((rin.r[4]!=-1) && (kerr==NULL)) {
  268.               kerr=_kernel_swi(OS_GBPB,&rin,&rout);
  269.               if (kerr==NULL) {
  270.                 fc+=rout.r[3];
  271.                 rin.r[4]=rout.r[4];
  272.               }
  273.             }
  274.             if (fc<dsize) {
  275.               found=TRUE;
  276.             } else {
  277.               subnum++;
  278.             }
  279.           }
  280.           tptr[2]='.';
  281.           tptr[3]='\0';
  282.         } /* if split */
  283.         strcpy(roname,scratch);
  284.         tptr=roname+strlen(scratch);
  285.         if (type!=NULL) {
  286.           strcpy(tptr,type);
  287.           tptr+=strlen(type);
  288.           *tptr++='.';
  289.         }
  290.         strcpy(tptr,name);
  291.       } else {
  292.         /* else roname=directory.name/type */
  293.         if (direct!=NULL) {
  294.           strcpy(roname,direct);
  295.           tptr=roname+strlen(direct);
  296.           *tptr++='.';
  297.         } else {
  298.           tptr=roname;
  299.         }
  300.         strcpy(tptr,name);
  301.         if (type!=NULL) {
  302.           tptr=tptr+strlen(name);
  303.           *tptr++='/';
  304.           strcpy(tptr,type);
  305.         }
  306.       } /* if type in types list */
  307.     } /* type is not null */
  308.   } else {
  309.     strcpy(roname,direct);
  310.   } /* if just a directory */
  311.   if (name!=NULL) {
  312.     return(TRUE);
  313.   } else {
  314.     return(FALSE);
  315.   }
  316. }
  317.  
  318.  
  319. int main(int argc, char **argv) {
  320.   char *tarfile=NULL;
  321.   char *tfile=NULL;
  322.   int tabort=FALSE;
  323.   FILE *fhandle;
  324.   FILE *ohandle;
  325.   FILE *lhandle;
  326.   char line[MAXLEN];
  327.   char *lc=NULL;
  328.   int ln, titem;
  329.   HBlock block;
  330.   _kernel_oserror *kerr;
  331.   _kernel_swi_regs rin, rout;
  332.   int filesize;
  333.   char filename[MAXLEN];
  334.   char linkname[MAXLEN];
  335.   int endblock;
  336.   char *cptr;
  337.  
  338.   /* Initialise global data */
  339.   verbose=FALSE;
  340.   list=FALSE;
  341.   dsize=-1;
  342.   crunch=FALSE;
  343.   tc=0;
  344.   prefix=NULL;
  345.  
  346.   /* Process arguments */
  347.   argv++; /* skip filename */
  348.   while (argc>0) {
  349.     switch (argv[0][0]) {
  350.     case '-':
  351.       switch ((argv[0][1] | 0x20)) {
  352.       case 'v':
  353.         verbose=TRUE;
  354.         break;
  355.       case 'l':
  356.         list=TRUE;
  357.         break;
  358.       case 'c':
  359.         crunch=TRUE;
  360.         break;
  361.       case 'd':
  362.         if (dsize!=-1) {
  363.           (void) fprintf(stderr, "ExTARct: Only one -d value, please\n");
  364.           tabort=TRUE;
  365.         } else {
  366.           if (argc>1) {
  367.             dsize=atoi(argv[1]);
  368.             argv++;
  369.             argc--;
  370.           } else {
  371.             (void) fprintf(stderr, "ExTARct: -d requires parameter (number)\n");
  372.             tabort=TRUE;
  373.           }
  374.         }
  375.         break;
  376.       case 't':
  377.         if (tfile!=NULL) {
  378.           (void) fprintf(stderr, "ExTARct: Only one tfile, please\n");
  379.           tabort=TRUE;
  380.         } else {
  381.           if (argc>1) {
  382.             tfile=argv[1];
  383.             argv++;
  384.             argc--;
  385.           } else {
  386.             (void) fprintf(stderr, "ExTARct: -t requires parameter (filename)\n");
  387.             tabort=TRUE;
  388.           }
  389.         }
  390.         break;
  391.       case 'p':
  392.         if (prefix!=NULL) {
  393.           (void) fprintf(stderr, "ExTARct: Only one -p value, please\n");
  394.           tabort=TRUE;
  395.         } else {
  396.           if (argc>1) {
  397.             prefix=argv[1];
  398.             argv++;
  399.             argc--;
  400.           } else {
  401.             (void) fprintf(stderr, "ExTARct: -p requires parameter (prefix string)\n");
  402.             tabort=TRUE;
  403.           }
  404.         }
  405.         break;
  406.       default:
  407.         (void) fprintf(stderr, "ExTARct: Bad option %c\n", argv[0][1]);
  408.         tabort=TRUE;
  409.       }
  410.       break;
  411.     default:
  412.       /* Must be filename */
  413.       if (argv[0]!=NULL) tarfile=argv[0];
  414.     }
  415.     argv++;
  416.     argc--;
  417.   }
  418.   if (dsize==-1) dsize=60;
  419.  
  420.   /* Read configuration file */
  421.   if (tfile != NULL) {
  422.     if ((fhandle=fopen(tfile,"r"))==NULL) {
  423.       (void) fprintf(stderr, "ExTARct: Open failed for T file\n");
  424.       tabort=TRUE;
  425.     }
  426.     while (!feof(fhandle) && !tabort) {
  427.       if (fgets(line, MAXLEN, fhandle)==NULL) {
  428.         if (!feof(fhandle)) {
  429.           (void) fprintf(stderr, "ExTARct: Read failed for T file\n");
  430.           tabort=TRUE;
  431.         }
  432.       } else {
  433.         lc=line;
  434.         while (*lc==' ' || *lc=='\t') lc++;
  435.         if (*lc!='#') {
  436.           splits[tc]=FALSE;
  437.           ln=0;
  438.           while (lc[ln]>' ') ln++;
  439.           types[tc]=malloc(ln+1);
  440.           strncpy(types[tc],lc,ln);
  441.           types[tc][ln]='\0';
  442.           while (lc[ln]==' ' || lc[ln]=='\t') ln++;
  443.           if (lc[ln]>' ') {
  444.             switch (lc[ln]) {
  445.             case '*':
  446.               splits[tc]=TRUE;
  447.               break;
  448.             default:
  449.               (void) fprintf(stderr, "ExTARct: Bad switch in Tfile (%c)\n", lc[ln]);
  450.               tabort=TRUE;
  451.             }
  452.           }
  453.           tc++;
  454.         }
  455.       }
  456.     }
  457.     fclose(fhandle);
  458.   }
  459.  
  460.   /* tarfile MUST be present :-)  Don't really want (can't be bothered...) to use stdin by default */
  461.   if (tarfile==NULL) {
  462.     (void) fprintf(stderr, "ExTARct: Tar file is mandatory\n");
  463.     tabort=TRUE;
  464.   }
  465.  
  466.   /* Display accumulated settings */
  467.   if (verbose) {
  468.     (void) fprintf(stdout, "ExTARct v0.01 (C) 1995 Kevin F. Quinn\n  Email kevq@banana.demon.co.uk\n\n");
  469.     (void) fprintf(stdout, "Settings:\n");
  470.     (void) fprintf(stdout, "  Directory limit %d\n", dsize);
  471.     (void) fprintf(stdout, "  Verbose ON\n");
  472.     (void) fprintf(stdout, "  Prefix: %s\n",(prefix==NULL)?"(none)":prefix);
  473.     (void) fprintf(stdout, "  Types:%s\n",(tc==0)?" (none)":"");
  474.     titem=0;
  475.     while (titem<tc) {
  476.       (void) fprintf(stdout, "    %s%s\n",types[titem],splits[titem]?" (split)":"");
  477.       titem++;
  478.     }
  479.   }
  480.  
  481.   /* Report error and exit, if anything untoward was detected when reading options */
  482.   if (tabort) {
  483.     (void) fprintf(stderr, "Syntax:\n   ExTARct [options] <tarfile>");
  484.     (void) fprintf(stderr, "  where options are as follows:\n");
  485.     (void) fprintf(stderr, "    -v            The ubiquitous verbose option\n");
  486.     (void) fprintf(stderr, "    -d <number>   The number of files to one directory (default 60)\n");
  487.     (void) fprintf(stderr, "    -t <filename> Filename listing types to swap, tagged for directory split\n");
  488.     (void) fprintf(stderr, "                  (default is no swaps, no directory splits)\n");
  489.     (void) fprintf(stderr, "    -p <prefix>   Prefix for split subdirectories (default none)\n");
  490.     (void) fprintf(stderr, "    -l            List operations (i.e. filename mappings) only\n");
  491.     return(EXIT_FAILURE);
  492.   }
  493.  
  494.  
  495.   /* Now on to the real business of extracting the archive */
  496.   if ((fhandle=fopen(tarfile,"rb"))==NULL) {
  497.     (void) fprintf(stderr, "Unable to open file %s\n", tarfile);
  498.     return(EXIT_FAILURE);
  499.   } /* if fhandle == NULL */
  500.   endblock=FALSE;
  501.   while (!feof(fhandle) && !tabort && !endblock) {
  502.     if (fread(&block, sizeof(HBlock), 1, fhandle)!=1) {
  503.       (void) fprintf(stderr, "Bad block; must be 512 bytes\n");
  504.       tabort=TRUE;
  505.     } else {
  506.       if (block.dbuf.name[0]=='\0') endblock=TRUE;
  507.       if (!endblock) {
  508.         if (!translatename(block.dbuf.name, filename)) { /* directory only*/
  509.           if (list) {
  510.             (void) fprintf(stdout, "Directory %s\n",filename);
  511.           } else {
  512.             if (verbose) (void) fprintf(stdout, "Directory %s\n",filename);
  513.             strcpy(line,filename); /* Need to tag '.' on end of directory */
  514.             cptr=line+strlen(filename);
  515.             *cptr++='.';
  516.             *cptr='\0';
  517.             if (!makedir(line)) {
  518.               (void) fprintf(stdout, "Unable to create directory %s (%s)\n",
  519.                   filename,block.dbuf.name);
  520.             }
  521.           }
  522.         } else {
  523.           if (block.dbuf.linkflag=='1' || block.dbuf.linkflag=='2') {
  524.             /* Entry is a link.
  525.              * Create a LinkFS image.  These are files of type FC0, which simply
  526.              * contain the canonical name of the file, followed by a '.' and a '\0'.
  527.              * Only symbolic links are available, so I generate a symbolic link for hard links.
  528.              */
  529.             translatename(block.dbuf.linkname, linkname);
  530.             if (list) {
  531.               (void) fprintf(stdout, "Linking %s to %s (original names %s and %s)\n",
  532.                   filename,linkname,block.dbuf.name,block.dbuf.linkname);
  533.             } else {
  534.               if (verbose)
  535.                   (void) fprintf(stdout, "Linking %s to %s (original names %s and %s)\n",
  536.                       filename,linkname,block.dbuf.name,block.dbuf.linkname);
  537.               if ((lhandle=fopen(linkname,"wb"))==NULL) {
  538.                 /* Try to create directory (if relevant) - note this is necessary for the "sub"ed files */
  539.                 if (!makedir(linkname)) {
  540.                   (void) fprintf(stderr, "Unable to create directory for link %s (to %s)\n",linkname,filename);
  541.                   tabort=TRUE;
  542.                 } else {
  543.                   if ((lhandle=fopen(linkname,"wb"))==NULL) {
  544.                     (void) fprintf(stderr, "Unable to create link %s (to %s)\n",linkname,filename);
  545.                     tabort=TRUE;
  546.                   }
  547.                 }
  548.               } /* ohandle==NULL*/
  549.               if (!tabort) {
  550.                 /* Write link */
  551.                 fputs(filename,lhandle);
  552.                 fputc('.',lhandle);
  553.                 fputc('\0',lhandle);
  554.                 fclose(lhandle);
  555.                 /* Settype to 0xFC0 */
  556.                 rin.r[0]=18;
  557.                 rin.r[1]=(int)linkname;
  558.                 rin.r[2]=0xFC0;
  559.                 kerr=_kernel_swi(OS_File, &rin, &rout);
  560.                 if (kerr!=NULL)
  561.                     (void) fprintf(stderr, "Unable to settype %s to 0xFC0\n",linkname);
  562.               } /* if lhandle==NULL */
  563.             } /* if list */
  564.           } else {
  565.             filesize=otoi(block.dbuf.size);
  566.             if (list) {
  567.               (void) fprintf(stdout, "Creating %s (original name %s)\n",
  568.                   filename,block.dbuf.name);
  569.               while (filesize>0 && !tabort) {
  570.                 if (fread(&block, sizeof(HBlock), 1, fhandle)!=1) {
  571.                   (void) fprintf(stderr, "Read failed\n");
  572.                   tabort=TRUE;
  573.                 } /* if fread(block) */
  574.                 filesize=(filesize>TBLOCK)?(filesize-TBLOCK):0;
  575.               } /* while filesize>0 */
  576.             } else {
  577.               if (verbose)
  578.                   (void) fprintf(stdout, "Creating %s (original name %s)\n",
  579.                       filename,block.dbuf.name);
  580.               if ((ohandle=fopen(filename,"wb"))==NULL) {
  581.                 /* Try to create directory (if relevant) - note this is necessary for the "sub"ed files */
  582.                 if (!makedir(filename)) {
  583.                   (void) fprintf(stderr, "Couldn't create directory for %s\n", filename);
  584.                   tabort=TRUE;
  585.                 } else {
  586.                   if ((ohandle=fopen(filename,"wb"))==NULL) {
  587.                     (void) fprintf(stderr, "Couldn't create %s\n", filename);
  588.                     tabort=TRUE;
  589.                   }
  590.                 }
  591.               } /* ohandle==NULL*/
  592.               if (!tabort) {
  593.                 while (filesize>0 && !tabort) {
  594.                   if (fread(&block, sizeof(HBlock), 1, fhandle)!=1) {
  595.                     (void) fprintf(stderr, "Read failed\n");
  596.                     tabort=TRUE;
  597.                   } else {
  598.                     if (fwrite(&block, (filesize>TBLOCK)?TBLOCK:filesize, 1, ohandle)!=1) {
  599.                       (void) fprintf(stderr, "Write failed\n");
  600.                       tabort=TRUE;
  601.                     } /* if fwrite(block) */
  602.                     filesize=(filesize>TBLOCK)?(filesize-TBLOCK):0;
  603.                   } /* if fread(block) */
  604.                 } /* while filesize>0 */
  605.                 fclose(ohandle);
  606.               } /* !tabort */
  607.             } /* if list */
  608.           } /* if not a link */
  609.         } /* if directory */
  610.       } /* if block.dbuf.name[0]!='\0' */
  611.     } /* if fread(block) */
  612.   } /* while (!end of file) */
  613.   if (tabort) {
  614.     (void) fprintf(stderr, "Processing aborted\n");
  615.     return(EXIT_FAILURE);
  616.   } /* tabort */
  617.   return(EXIT_SUCCESS);
  618. }
  619.